package com.sinotn.ks.node.camera.impl;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import com.sinotn.SinotnLogger;
import com.sinotn.gson.GsonFacade;
import com.sinotn.ks.node.camera.ImageAnalyser;
import com.sinotn.ks.node.camera.bean.MatchReqBean;
import com.sinotn.ks.node.camera.bean.MatchRspBean;
import com.sinotn.ks.node.camera.service.CameraService;
import com.sinotn.ks.node.camera.vo.CameraPhotoVO;
import com.sinotn.ks.node.runtime.FileStoreUtil;

public class ImageAnalyserBaidu implements ImageAnalyser, Runnable {
	private static final Logger LOG=LoggerFactory.getLogger("com.sinotn.baidu");
	private static final byte MATCH_PASS=2;
	private static final byte MATCH_WARN=1;
	@Autowired
	private CameraService cameraService;
	private LinkedList<CameraPhotoVO> tmps=new LinkedList<CameraPhotoVO>();
	private ConcurrentHashMap<String, Boolean> idRefs=new ConcurrentHashMap<String, Boolean>(300);
	private LinkedList<CameraPhotoVO> doing;
	private ReentrantLock lockTmps=new ReentrantLock(false);
	private ReentrantLock lockRun=new ReentrantLock(false);
	private boolean running=false;
	private CloseableHttpClient client=null;
	private String appKey;
	private String appSecret;
	private int similarThreshold=80;
	private String token;
	private long lastTokenTime;
	private int errCount;
	private int errThreshold=300;
	private static final String urlToken="https://aip.baidubce.com/oauth/2.0/token";
	private static final String urlMatch="https://aip.baidubce.com/rest/2.0/face/v3/match?access_token=";

	public void setAppKey(String appKey) {
		this.appKey = appKey;
	}
	public void setAppSecret(String appSecret) {
		this.appSecret = appSecret;
	}
	public void setSimilarThreshold(int similarThreshold) {
		this.similarThreshold = similarThreshold;
	}
	public void setErrThreshold(int errThreshold) {
		this.errThreshold = errThreshold;
	}
	@Override
	public void analyse(CameraPhotoVO cameraPhotoVO) {
		if(this.errCount>this.errThreshold) return;
		if(this.idRefs.containsKey(cameraPhotoVO.getId())) return;
		this.lockTmps.lock();
		try{
			this.tmps.add(cameraPhotoVO);
			this.idRefs.put(cameraPhotoVO.getId(), Boolean.TRUE);
		}finally{
			this.lockTmps.unlock();
		}
		this.lockRun.lock();
		try{
			if(!this.running){
				this.start();
			}
		}finally{
			this.lockRun.unlock();
		}
	}
	private void start() {
		Thread t=new Thread(this);
		t.start();
		this.running=true;
	}
	
	private void closeClient(){
		if(this.client!=null){
			try{
				this.client.close();
			}catch(Throwable e){}
			this.client=null;
		}
	}

	@Override
	public void run() {
		try{
			Thread.sleep(1000);
			long idleTime=-1;
			long duration;
			final long threholdTime=1*60*1000;// 1分钟空闲退出
			CameraPhotoVO vo;
			while(true){
				if(this.doing==null || this.doing.isEmpty()){
					this.lockTmps.lock();
					try{
						if(this.tmps.size()>0){
							this.doing=this.tmps;
							this.tmps=new LinkedList<CameraPhotoVO>();
						}
					}finally{
						this.lockTmps.unlock();
					}
				}
				if(this.doing==null || this.doing.isEmpty()){
					// 无记录推送
					if(idleTime==-1){
						idleTime=System.currentTimeMillis();
					}else{
						duration=System.currentTimeMillis()-idleTime;
						if(duration>threholdTime){
							LOG.info("IdleExit|{}", idleTime);
							break;
						}else{
							this.closeClient();
							Thread.sleep(2000);
						}
					}
				}else{
					idleTime=-1;
					while(this.doing.size()>0){
						vo=this.doing.removeFirst();
						try{
							this.doSend(vo);
							this.errCount=0;
						}catch(Throwable e){
							SinotnLogger.DEBUG.error("比对照片{}相似度异常",vo.getId(), e);
							this.errCount++;
						}
						this.idRefs.remove(vo.getId());
						if(this.errCount>this.errThreshold){
							break;
						}
					}
				}
				if(this.errCount>this.errThreshold){
					break;
				}
			}
		}catch(Throwable e){
			SinotnLogger.DEBUG.error("百度照片分析线程错误", e);
		}finally{
			this.lockRun.lock();
			try{
				this.running=false;
				this.closeClient();
			}finally{
				this.lockRun.unlock();
			}
			if(this.errCount>this.errThreshold){
				LOG.info("照片比对任务失败次数{}超过阈值{}，退出比对任务", this.errCount, this.errThreshold);
			}
		}
	}
	
	private String getBase64(File file) throws Exception{
		FileInputStream fis=null;
		try {
			fis=new FileInputStream(file);
			byte[] data=new byte[fis.available()];
			fis.read(data);
			return Base64.encodeBase64String(data);
		}finally {
			IOUtils.closeQuietly(fis);
		}
	}
	
	private String getResponseText(HttpResponse resp) {
		int statusCode = resp.getStatusLine().getStatusCode();
		InputStream is = null;
		try {
			if (HttpStatus.SC_OK == statusCode) {
				is = resp.getEntity().getContent();
				InputStreamReader isr = new InputStreamReader(is, "utf-8");
				StringBuilder sb = new StringBuilder();
				char[] buf = new char[128];
				for (int len = isr.read(buf); len != -1; len = isr.read(buf)) {
					sb.append(buf, 0, len);
				}
				is.close();
				return sb.toString().trim();
			} else {
				EntityUtils.consumeQuietly(resp.getEntity());
				System.out.println("请求响应状态异常:" + statusCode);
				throw new RuntimeException("请求响应状态异常:" + statusCode);
			}
		}catch(RuntimeException e){
			throw e;
		}catch (Throwable e) {
			throw new RuntimeException(e);
		} finally {
			if (is != null) {
				try {
					is.close();
				} catch (Exception e) {
				}
			}
		}
	}
	
	private void doSend(CameraPhotoVO vo)  throws Exception{
		String roomId = vo.getRoomId(); // 考场ID
		// 报名图片保存路径，如果没有报名图片文件夹，则退出循环，不作对比
		File folder = FileStoreUtil.getPhotoFolder(roomId, false);
		if (folder == null || !folder.exists()) {
			this.cameraService.modifyAnalyse(vo.getId(), (byte)4, -1, -1);
			return;
		}
		String examineeId = vo.getExamineeId(); // 考生ID
		final File signPhoto = new File(folder, FileStoreUtil.getPhotoName(examineeId));
		if (!signPhoto.exists()){
			this.cameraService.modifyAnalyse(vo.getId(), (byte)4, -1, -1);
			return;
		}
		// 监考照片
		File cameraFolder = FileStoreUtil.getStoreFolder(roomId, examineeId, false);//new File(, "camera");// 摄像照片
		if (cameraFolder == null || !cameraFolder.exists()) {
			this.cameraService.modifyAnalyse(vo.getId(), (byte)4, -1, -1);
			return;
		}
		final File cameraPhoto = new File(cameraFolder, "camera/" + vo.getPhotoPath());
		if (!cameraFolder.exists()) {
			this.cameraService.modifyAnalyse(vo.getId(), (byte)4, -1, -1);
			return;
		}
		double similar=this.getSimilar(vo.getId(), signPhoto, cameraPhoto);
		if(similar<this.similarThreshold){
			this.cameraService.modifyAnalyse(vo.getId(), MATCH_WARN, 0, (int)Math.round(similar*10));
		}else{
			this.cameraService.modifyAnalyse(vo.getId(), MATCH_PASS, 0, (int)Math.round(similar*10));
		}
	}
	private void checkToken() throws Exception{
		if(this.client==null){
			
			SSLContext ctx = SSLContext.getInstance("TLS");
	        X509TrustManager tm = new X509TrustManager() {
	                @Override
	                public void checkClientTrusted(X509Certificate[] chain,
	                        String authType) throws CertificateException {
	                }
	                @Override
	                public void checkServerTrusted(X509Certificate[] chain,
	                        String authType) throws CertificateException {
	                }
	                @Override
	                public X509Certificate[] getAcceptedIssuers() {
	                    return null;
	                }
	        };
	        ctx.init(null, new TrustManager[]{tm}, null);
	        this.client=HttpClientBuilder.create().setSSLContext(ctx).build();
	        /*
			this.client=HttpClientBuilder.create().build();
			*/
		}
		if(this.token!=null && this.lastTokenTime>System.currentTimeMillis()){
			return;
		}
		HttpPost post=null;
		try{
			post=new HttpPost(urlToken);
			ArrayList<NameValuePair> params = new ArrayList<NameValuePair>(3);
			params.add(new BasicNameValuePair("grant_type", "client_credentials"));
			params.add(new BasicNameValuePair("client_id", this.appKey));
			params.add(new BasicNameValuePair("client_secret", this.appSecret));
			UrlEncodedFormEntity entity=new UrlEncodedFormEntity(params,"UTF-8");
			post.setEntity(entity);
			HttpResponse resp=client.execute(post);
			String responseText=this.getResponseText(resp);
			post.releaseConnection();
			post=null;
			if(null==responseText){
				throw new RuntimeException(urlToken+"请求返回NULL");
			}else{
				Map<?, ?> result=GsonFacade.fromJson(responseText, HashMap.class);
				this.token=(String)result.get("access_token");
				if(this.token==null){
					throw new RuntimeException(urlToken+"获取token失败"+responseText);
				}
				int duration=((Number)result.get("expires_in")).intValue();
				this.lastTokenTime=(System.currentTimeMillis()+duration*1000)-10000;
			}
		}finally{
			if(post!=null){
				post.releaseConnection();
			}
		}
	}
	private double getSimilar(String id, File signPhoto, File cameraPhoto) throws Exception{
		this.checkToken();
		String url=urlMatch+this.token;
		HttpPost post=null;
		InputStream is=null;
		try{
			ArrayList<MatchReqBean> reqs=new ArrayList<MatchReqBean>(2);
			MatchReqBean req=new MatchReqBean();
			req.setImage(this.getBase64(signPhoto));
			reqs.add(req);
			
			req=new MatchReqBean();
			req.setImage(this.getBase64(cameraPhoto));
			reqs.add(req);
			
			post=new HttpPost(url);
			StringEntity entity=new StringEntity(GsonFacade.toJson(reqs), "UTF-8");
			post.setEntity(entity);
			post.setHeader("Content-Type", "application/json");
			HttpResponse resp=client.execute(post);
			String responseText=this.getResponseText(resp);
			post.releaseConnection();
			post=null;
			if(null==responseText){
				throw new RuntimeException(url.toString()+"请求返回NULL");
			}else {
				/*
				 {"error_code":0,
				 "error_msg":"SUCCESS",
				 "log_id":934150895,
				 "timestamp":1650539734,
				 "cached":0,
				 "result":{
				 	"score":28.80872345,
				 	"face_list":[
				 		{"face_token":"7432bab03e1017a6bb7285c6cffcbab0"},
				 		{"face_token":"2a876a470a514843f31005e6dc051a86"}
				 		]
				 	}
				 }
				 */
				MatchRspBean rsp=GsonFacade.fromJson(responseText, MatchRspBean.class);
				int errCode=rsp.getError_code();
				if(errCode==0){
					return rsp.getResult().getScore();
				}
				LOG.info("{}比对响应异常[code={},msg={}]", id, rsp.getError_code(), rsp.getError_msg());
				return 0;
			}
		}finally{
			if(post!=null){
				post.releaseConnection();
			}
			IOUtils.closeQuietly(is);
		}
	}
}
